package com.garvin.gateway.controller;

import com.garvin.dto.base.BaseResponseDto;
import com.garvin.enums.HttpResponseCodeEnum;
import com.garvin.gateway.service.GatewayRouteService;
import org.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author : garvin
 * @version V1.0
 * @date : 2020年10月09日 17:00
 */
@RestController
@RequestMapping("/dynamic/gateway")
public class GatewayController extends AbstractGatewayControllerEndpoint {

    @Resource
    private GatewayRouteService gatewayRouteService;

    public GatewayController(List<GlobalFilter> globalFilters,
                             List<GatewayFilterFactory> gatewayFilters,
                             List<RoutePredicateFactory> routePredicates,
                             RouteDefinitionWriter routeDefinitionWriter,
                             RouteLocator routeLocator) {
        super(null, globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter, routeLocator);
    }

    @PostMapping("/routes/add/{id}")
    public Mono<ResponseEntity<BaseResponseDto>> routesAdd(@PathVariable String id, @RequestBody RouteDefinition route) {
        route.setId(id);
        BaseResponseDto responseDto = gatewayRouteService.saveRouteDefinition(route);
        if (HttpResponseCodeEnum.SUCCESS.getCode().equals(responseDto.getCode())) {
            super.refresh();
        }
        return Mono.just(new ResponseEntity<>(responseDto, HttpStatus.CREATED));
    }

    @PostMapping("/routes/delete/{id}")
    public Mono<ResponseEntity<Object>> routesDelete(@PathVariable String id) {
        BaseResponseDto responseDto = gatewayRouteService.deleteRouteDefinition(id);
        if (HttpResponseCodeEnum.SUCCESS.getCode().equals(responseDto.getCode())) {
            super.refresh();
        }
        return Mono.just(new ResponseEntity<>(responseDto, HttpStatus.OK));
    }

    @PostMapping("/routes/update/{id}")
    public Mono<ResponseEntity<Object>> routesUpdate(@PathVariable String id, @RequestBody RouteDefinition route) {
        route.setId(id);
        BaseResponseDto responseDto = gatewayRouteService.saveRouteDefinition(route);
        if (HttpResponseCodeEnum.SUCCESS.getCode().equals(responseDto.getCode())) {
            super.refresh();
        }
        return Mono.just(new ResponseEntity<>(responseDto, HttpStatus.OK));
    }

    @GetMapping("/routes")
    public Flux<Map<String, Object>> routes() {
        return this.routeLocator.getRoutes().map(this::serialize);
    }

    @GetMapping("/routes/{id}")
    public Mono<ResponseEntity<Map<String, Object>>> route(@PathVariable String id) {
        return this.routeLocator.getRoutes()
                .filter(route -> route.getId().equals(id))
                .singleOrEmpty()
                .map(this::serialize)
                .map(ResponseEntity::ok)
                .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
    }

    private Map<String, Object> serialize(Route route) {
        HashMap<String, Object> r = new HashMap<>();
        r.put("route_id", route.getId());
        r.put("uri", route.getUri().toString());
        r.put("order", route.getOrder());
        r.put("predicate", route.getPredicate().toString());
        if (!CollectionUtils.isEmpty(route.getMetadata())) {
            r.put("metadata", route.getMetadata());
        }
        ArrayList<String> filters = new ArrayList<>();
        for (int i = 0; i < route.getFilters().size(); i++) {
            GatewayFilter gatewayFilter = route.getFilters().get(i);
            filters.add(gatewayFilter.toString());
        }
        r.put("filters", filters);
        return r;
    }
}
